简体   繁体   中英

How do I diagnose and remove an ambiguous reference in C#?

(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

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.

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM