2

(Please feel free to suggest a more accurate title to this question.)

In my Visual Studio 2015 solution, I have three projects (let's call them Alpha, Beta, and Gamma) that are more or less the same thing, but differ in that they define different backends. Both of these projects hot-plug a class into the same namespace:

Alpha:

namespace SharedNamespace {
    public class SharedClass {
        // implement SharedClass using Alpha's backend
    }
}

Beta:

namespace SharedNamespace {
    public class SharedClass {
        // implement SharedClass using Beta's backend
    }
}

Gamma:

namespace SharedNamespace {
    public class SharedClass {
        // implement SharedClass using Gamma's backend
    }
}

Several projects use this hot-plugged class, each referencing either Alpha, Beta, or Gamma. One of them (let's call it Omricon) used to reference Alpha, but now references Gamma:

// ...
SharedNamespace.SharedClass sharedClass;
sharedClass.DoThing();
// ...

When I attempt to build Omricon, however, the C# compiler gives error CS0433:

The type 'SharedClass' exists in both 'Alpha, Version=0.0.0.0 (etc)' 
and 'Gamma, Version=0.0.0.0 (etc)'

However, Omricon only references Gamma when it is built - when I go into the project references list, only the reference to Gamma appears. As far as I understand it, Omricon should know nothing about Alpha at all, much less that it defines a class in the same location. Only Omricon fails to build - other projects that use Alpha and Beta work fine, and when I switch Omricon back to using Alpha, it works fine as well!

It appears to me that a reference to Alpha is being maintained, then, somewhere else. How can I find the stray reference to Alpha, wherever it lies in my code, and remove it?

Note that I have tried forcing a full rebuild (as this answer suggested), and the same error still appears, so it has nothing to do with bad object caching.

EDIT: clarified second to last paragraph

Community
  • 1
  • 1
TheHans255
  • 2,059
  • 1
  • 19
  • 36
  • Are both of the projects (Gamma and Alpha) in the same solution? – David L Sep 07 '16 at 17:20
  • 4
    Why are you not using different namespaces, given that these classes conceptually exist in a different space? – Servy Sep 07 '16 at 17:21
  • @DavidL Yes, they are. – TheHans255 Sep 07 '16 at 17:22
  • 4
    Well there's your problem. You are trying to build classes with the same names in the same namespace in the same solution. Why ***wouldn't*** you expect this to fail? – David L Sep 07 '16 at 17:23
  • @Servy (1) This wasn't my decision, (2) The code that uses `SharedClass` is generated. – TheHans255 Sep 07 '16 at 17:24
  • @DavidL Alpha and Beta have both built fine, defined in just the same way, for years. I have only recently added Gamma, and only the project that references Gamma fails to build. – TheHans255 Sep 07 '16 at 17:25
  • @Compynerd255 then the decision was flawed. If this is supposed to work at all, they would need to be partial classes (which typically they should be if generated anyways). Most likely you have an issue with your generated code implementation and it is impossible to diagnose properly from what you've shown here. – David L Sep 07 '16 at 17:26
  • @Compynerd255 Then change the code that generates it to give each one a different namespace. Clearly you need to, to resolve the ambiguity, and it doesn't even make sense in the first place for them to all be in the same namespace – Servy Sep 07 '16 at 17:26
  • The error message tells you **exactly** what the problem is and where the error is located. What part of that very clearly written, very precise message is unclear? – Ken White Sep 07 '16 at 17:27
  • Did you try all of the answers from that question you linked to? Because sometimes you need to try more than just the accepted one... You can also take a look at [this question](http://stackoverflow.com/q/11989983/215552) as it seems very similar. – Heretic Monkey Sep 07 '16 at 17:27
  • @DavidL partial classes must exist in the same assembly, so they cannot be in different projects. – Racil Hilan Sep 07 '16 at 17:28
  • @RacilHilan correct. I never said it would work with the OP's current setup. I said that typically that would be the approach for declaring same name same namespace classes. Why this is even being done in the first place is beyond me. – David L Sep 07 '16 at 17:32
  • All the three projects can easily be built by themselves, it is when you start adding references to more than 1 of them to the same fourth project you get into problems. You say that it "used to reference Alpha", clearly it still references Alpha so remove that reference and rebuild. – Lasse V. Karlsen Sep 07 '16 at 21:35
  • @LasseV.Karlsen I did remove that reference - I stare at the .csproj file and the reference to Alpha is completely gone and completely replaced by the reference to Gamma, complete with path and GUID. The conceit of this question is if there could be some other process or thing that still remembered the reference somehow. – TheHans255 Sep 07 '16 at 21:38

4 Answers4

15

First off, as you probably have realized: this is a terrible situation to be in. If you can possibly avoid having the same named class in the same named namespace in two different assemblies that you reference both of them, avoid that situation. It is strongly indicative of an architectural flaw in your application. It sounds to me like you should be defining an interface in yet a fourth assembly, and then have all your assemblies agree to use that interface.

However, there is a way to deal with this situation in C#.

When you compile Omicron, you should give Alpha.dll, Beta.dll and Gamma.dll a reference alias:

/reference:AlphaDLL=Alpha.DLL /reference:BetaDLL=Beta.DLL ... etc

then inside Omicron you say:

extern alias AlphaDLL;
extern alias BetaDLL;
extern alias GammaDLL;

In a file, and then later in that file you can say:

AlphaDLL::SharedNamespace.SharedClass

in order to disambiguate which one is intended.

But again, do not get into this situation. Instead make an interface that SharedClass implements, and have the Alpha, Beta and Gamma implementations all implement that interface with a class whose name does not conflict.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • What would I do if the project is defined in PackageReference? To my surprise, adding aliases, in this case, does not work at all. – Margus Aug 13 '21 at 14:46
1

So, after a little digging with a teammate, I found out that even though a reference to Alpha was not found in the project file itself, one of our .targets files was directing MSBuild to add a project reference to Alpha behind my back:

<Choose>
    <When Condition=" <!-- needs beta --> ">
        <ItemGroup>
            <ProjectReference Include="$(absdtSln)path\to\Beta">
                ...
            </ProjectReference>
        </ItemGroup>
    </When>
    <Otherwise>
        <ItemGroup>
            <ProjectReference Include="$(absdtSln)path\to\Alpha">
                ...
            </ProjectReference>
        </ItemGroup>
    </Otherwise>
</Choose>

(This, I assume, is so that projects that reference Alpha and Beta don't have to do so manually, as I was trying to do and was explicitly done on the project I was testing).

I added another case for Gamma and things work now.

(And yes, @Eric, stuff like this is yet another testament that this is a terrible situation to be in.)

Community
  • 1
  • 1
TheHans255
  • 2,059
  • 1
  • 19
  • 36
  • That's exactly what I expected. And no, not a terrible situation to be in. It is a completely valid case as I explained in my answer. The only "terrible" issue is that it seems it is not documented as it is often the case that many teams don't care to create the proper documentation. In all projects I work on, I create the required guides (for dev, test, and live) and every new developer has to read the dev guides before touching the code. If your team had such rules, you wouldn't have ended up wasting your time like that. That's why I gave you my answer from experience. – Racil Hilan Sep 07 '16 at 21:27
  • @RacilHilan That's a good point - it does work, as you said, and it does fit very well. I only say this situation is terrible (which is probably an exaggeration) because this sort of system is more difficult to document than something based in the language itself, such as an interface or strategy pattern. Those sorts of things are less subject to Murphy's Law and thus require less documentation in the first place. – TheHans255 Sep 07 '16 at 21:30
  • Yes, I fully agree with that, but we all know that we often find ourselves restricted by some weird requirements coming from an external library, framework, or tool that we have to use in our projects. Or sometimes it's a good existing code that we don't want to change much. You said that the code was "generated", so perhaps your team had resorted to the MS-Build technic for some reason. Anyway, I'm glad that you figured it out. – Racil Hilan Sep 07 '16 at 21:41
0

Classes with the same name can exist in different projects, but they cannot belong to the same namespace.

Since you said that Alpha and Beta have been building successfully and the issue started when added Gamma, I suspect that Alpha and Beta where built separately by disabling one while building the other, and vice versa. Check with somebody in your team who's familiar with how they were built in the past.

I think the reason behind that setup is so you create two dlls (Alpha and Beta) with the same class name, so they can be called and used in the same way. That creates two dlls with the same signature to do different things.

Your message somewhat confirms my suspicion as you get an issue with Alpha and Gamma, but not Beta. I think Beta was disabled in preparation to build Alpha when you added Gamma.

Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • Yes, that is exactly the purpose - to create two different DLLs with the same signature, in order to do different things. I'll comment again with a note on build controls, but I can't seem to find any other than the fact that both are never used together in any project. I should duly note, however, that Beta is a Universal Windows library, which might have an effect on this. – TheHans255 Sep 07 '16 at 17:50
-1

As what the others in the comments have said, what you are doing is impossible... You cannot have two classes with the exact same name in the exact same namespace (unless they are partials).

You have a few options here:

  1. Use different namespaces

The benefit is that you don't have these collisions and can specify your class instances by their fully qualified namespace.

Alpha:

namespace AlphaNamespace {
    public class SharedClass {
        // implement SharedClass using Alpha's backend
    }
}

Beta:

namespace BetaNamespace {
    public class SharedClass {
        // implement SharedClass using Beta's backend
    }
}

Gamma:

namespace GammaNamespace {
    public class SharedClass {
        // implement SharedClass using Gamma's backend
    }
}

Here, omicron would then use gamma as

var gamma = new GammaNamespace.SharedClass(...)

  1. Use partials

Partial Class Documentation

You'd have to merge projects and change method names to avoid collisions when the compiler melds the two classes together. Doubt this'll work the way you want it to...

Alpha:

namespace SharedNamespace {
    public partial class SharedClass {
        // implement SharedClass using Alpha's backend
    }
}

Beta:

namespace SharedNamespace {
    public partial class SharedClass {
        // implement SharedClass using Beta's backend
    }
}

Gamma:

namespace SharedNamespace {
    public partial class SharedClass {
        // implement SharedClass using Gamma's backend
    }
}

  1. Use a strategy pattern (or state pattern with interfaces instead of concrete classes)

Here, you would determine which extension of BaseClass you wish to use (Alpha, Beta, or Gamma) and swap it out either through dependency injection or some sort of logical determination.

namespace SharedNamespace {
    public class BaseClass{
        // place method abstractions here
    }
}


namespace SharedNamespace {
    public class AlphaClass : BaseClasse {
        // implement BaseClass using Alpha's backend
    }
}

namespace SharedNamespace {
    public class BetaClass : BaseClasse {
        // implement BaseClass using Beta's backend
    }
}

namespace SharedNamespace {
    public class GammaClass : BaseClasse {
        // implement BaseClass using Gamma's backend
    }
}
Stephen P.
  • 2,221
  • 2
  • 15
  • 16