The problem of the owl and the globe: the connection of two assemblies with identical namespaces and class names


    Tonight, with gelas they started talking about how package managers work on different platforms. During the conversation, we got to the discussion of the situation when you need to connect two libraries to a project on .NET Core that contain classes with the same name in the same namespaces. Since I do the .NET Core quite tightly, I wanted to check how this problem can be solved. What came of it is described further


    Disclaimer . Do such situations often occur? For more than 10 years of working with .NET, I have never had to face such a situation in a real project. But the experiment was interesting.


    Just in case, I will clarify that I will conduct the experiment using:


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

    So, let's simulate a situation when we got two libraries in which we have the classes we need, which we should use in our project. At the same time, we do not have access to the source code, and we cannot decompile assemblies in order to change the namespaces in them, and then we cannot compile them back.


    Preparing the experiment


    And so, for the beginning we will prepare one owl and two globes. As an owl, we will have a project with a target on netcoreapp2.1. As globes we will create two projects, one of which will also be with the target for netcoreapp2.1, and the second for netstandard2.0



    In each project we put in the class Globe, which will be located in identical namespaces, but they will have different implementation:


    First file:


    using System;
    namespaceSpace
    {
        publicclassGlobe
        {
            publicstringGetColor() => "Green";
        }
    }

    Second file:


    using System;
    namespaceSpace
    {
        publicclassGlobe
        {
            publicstringGetColor() => "Blue";
        }
    }

    Attempt number one


    Since, according to the conditions of the problem, we have to work with external assemblies, rather than projects, we will add links to the project accordingly as if they really are just libraries. To do this, we first compile all the projects so that we have the Globe1.dll and Globe2.dll we need. Then add links to the project in the following form:



    Now let's try to create a variable of the class Globe:



    As you can see, already at this stage, the IDE warns us that there is a problem with understanding where the required Globe class should come from.


    At first it seems that the situation is quite typical and it should already be ready, cast in granite, the answer to the Stack Overflow. As it turned out, for the .NET Core solution of this problem has not yet been proposed. Either my Google let me down. But it was possible to find something useful on Stack Overflow. The only sensible publication that we managed to google was in 2006 and described a similar situation for the classic version of .NET. At the same time, a very similar problem is discussed in the repository of the NuGet project .


    There is not very much useful information, but it is still there:


    • In the classic version of .NET, the alias mechanism was implemented.
    • According to the specification, C # supports the use of aliases in code.

    It remains to understand how to do this in .NET Core.


    Unfortunately, the current version of the documentation rather modestly describes the possibilities of connecting external packages / fees. And the description of the csproj file also in no way sheds light on the possibility of creating pseudonyms. But nevertheless, through trial and error, I managed to find out that aliases for assemblies in .NET Core are still supported. And they are made out as follows:


    <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>

    Now it remains to learn how to use these pseudonyms. The previously mentioned extern keyword will help us with this :


    In the documentation about it write the following:


    In some cases, it may be necessary to provide references to two versions of assemblies with the same full type names. For example, you need to use two or more versions of an assembly in the same application. Using an external assembly alias, you can include namespaces for each assembly in a wrapper within the root-level namespaces named for this alias, which allows them to be used in a single file.
    ...
    Each time the extern alias is declared, an additional root-level namespace is introduced, which corresponds to the global namespace (but is not inside it). Thus, references to types from each assembly without ambiguity can be created using their full name, the root of which is the corresponding namespace alias.

    Here, however, we should not forget that extern is also used in C # to declare a method with an external implementation from unmanaged code. In this case, extern is usually used with the DllImport attribute. More details about this can be found in the relevant section of the documentation .


    So let's try to use our aliases:


    externalias Lib1;
    externalias Lib2;
    using System;
    namespaceOwl
    {   
    ...
        publicclassSuperOwl
        {
            private Lib1::Space.Globe _firstGlobe;
            private Lib2::Space.Globe _secondGlobe;
            publicvoidIntegrateGlobe(Lib1::Space.Globe globe) => _firstGlobe = globe;
            publicvoidIntegrateGlobe(Lib2::Space.Globe globe) => _secondGlobe = globe;
    ...
        }
    }

    This code is even working. And it works correctly. But still I want to make it a little more elegant. This can be done in a very simple way:


    externalias Lib1;
    externalias Lib2;
    using System;
    using SpaceOne=Lib1::Space;
    using SpaceTwo=Lib2::Space;

    Now you can use the usual and obvious syntax:


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

    Tests


    Let's test our owl:



    As you can see, the code worked fine and without errors. The integration of owls and globes successfully completed!


    → Sample code is available on GitHub


    Also popular now: