【问题标题】:How do I diagnose and remove an ambiguous reference in C#?如何在 C# 中诊断和删除不明确的引用?
【发布时间】:2016-09-07 17:18:18
【问题描述】:

(请随时为这个问题建议一个更准确的标题。)

在我的 Visual Studio 2015 解决方案中,我有三个项目(我们称它们为 Alpha、Beta 和 Gamma),它们或多或少是相同的,但不同之处在于它们定义了不同的后端。这两个项目都将一个类热插入到同一个命名空间中:

阿尔法:

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

测试版:

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

伽玛:

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

有几个项目使用这个热插拔类,每个都引用 Alpha、Beta 或 Gamma。其中一个(我们称之为 Omricon)曾经参考 Alpha,但现在参考 Gamma:

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

但是,当我尝试构建 Omricon 时,C# 编译器给出了错误 CS0433:

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

但是,Omricon 在构建时引用 Gamma - 当我进入项目引用列表时,只出现对 Gamma 的引用。据我了解,Omricon 应该对 Alpha 一无所知,更不用说它在同一位置定义了一个类。只有 Omricon 无法构建 - 使用 Alpha 和 Beta 的其他项目工作正常,当我将 Omricon 切换回使用 Alpha 时,它也工作正常!

在我看来,对 Alpha 的引用正在维护,然后,在其他地方。如何在代码中的任何位置找到对 Alpha 的杂散引用并将其删除?

请注意,我已尝试强制完全重建(如this answer 建议的那样),但仍然出现相同的错误,因此它与错误的对象缓存无关。

编辑:澄清倒数第二段

【问题讨论】:

  • 两个项目(Gamma 和 Alpha)是否在同一个解决方案中?
  • 既然这些类在概念上存在于不同的空间中,为什么不使用不同的命名空间?
  • @DavidL 是的。
  • 那是你的问题。您正在尝试在同一解决方案中的同一命名空间中构建具有相同名称的类。为什么不会您希望这会失败?
  • @Servy (1) 这不是我的决定,(2) 生成使用SharedClass 的代码。

标签: c# visual-studio-2015


【解决方案1】:

首先,您可能已经意识到:这是一个可怕的情况。如果您可以避免在您引用它们的两个不同程序集中的相同命名空间中具有相同的命名类,请避免这种情况。它强烈表明您的应用程序中存在架构缺陷。在我看来,您应该在第四个程序集中定义一个接口,然后让您的所有程序集同意使用该接口。

但是,有一种方法可以在 C# 中处理这种情况。

在编译 Omicron 时,应该给 Alpha.dll、Beta.dll 和 Gamma.dll 一个参考别名:

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

然后你在 Omicron 内部说:

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

在一个文件中,然后在该文件中你可以说:

AlphaDLL::SharedNamespace.SharedClass

为了消除歧义。

但同样,不要陷入这种情况。而是创建一个 SharedClass 实现的接口,并让 Alpha、Beta 和 Gamma 实现都使用名称不冲突的类实现该接口。

【讨论】:

  • 如果项目是在PackageReference中定义的怎么办?令我惊讶的是,在这种情况下,添加别名根本不起作用。
【解决方案2】:

所以,在与队友稍微挖掘之后,我发现即使在项目文件本身中没有找到对 Alpha 的引用,我们的 .targets 文件之一是指示 MSBuild 在后面添加对 Alpha 的项目引用我的背:

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

(我认为,这是为了让引用 Alpha 和 Beta 的项目不必手动执行此操作,正如我尝试做的并且在我正在测试的项目中明确完成)。

我为 Gamma 添加了另一个案例,现在一切正常。

(是的,@Eric,这样的事情再次证明这是一个可怕的情况。)

【讨论】:

  • 这正是我所期望的。不,这不是一个可怕的情况。正如我在回答中解释的那样,这是一个完全有效的案例。唯一“可怕”的问题是它似乎没有记录,因为许多团队通常不关心创建正确的文档。在我从事的所有项目中,我创建了所需的指南(用于开发、测试和实时),每个新开发人员在接触代码之前都必须阅读开发指南。如果你的团队有这样的规则,你就不会那样浪费时间了。这就是我根据经验给你答案的原因。
  • @RacilHilan 这是一个很好的观点 - 正如你所说,它确实有效,而且非常适合。我只是说这种情况很糟糕(这可能有点夸张),因为这种系统比基于语言本身的东西(例如接口或策略模式)更难记录。这类事情较少受墨菲定律的约束,因此首先需要较少的文档。
  • 是的,我完全同意这一点,但我们都知道,我们经常发现自己受到来自我们必须在项目中使用的外部库、框架或工具的一些奇怪要求的限制。或者有时它是一个很好的现有代码,我们不想改变太多。你说代码是“生成的”,所以也许你的团队出于某种原因求助于 MS-Build 技术。无论如何,我很高兴你想通了。
【解决方案3】:

同名的类可以存在于不同的项目中,但不能属于同一个命名空间。

既然您说 Alpha 和 Beta 已成功构建,并且在添加 Gamma 时问题就开始了,我怀疑 Alpha 和 Beta 是通过在构建另一个时禁用一个而单独构建的,反之亦然。请与您团队中熟悉过去构建方式的人员联系。

我认为该设置背后的原因是您创建了两个具有相同类名的 dll(Alpha 和 Beta),因此可以以相同的方式调用和使用它们。这会创建两个具有相同签名的 dll 来执行不同的操作。

您的消息在一定程度上证实了我的怀疑,因为您遇到了 Alpha 和 Gamma 问题,但不是 Beta。我认为当您添加 Gamma 时,Beta 已被禁用以准备构建 Alpha。

【讨论】:

  • 是的,这正是目的 - 创建两个具有相同签名的不同 DLL,以便执行不同的操作。我将再次评论构建控件的注释,但我似乎找不到任何其他事实,即两者从未在任何项目中一起使用。但是,我应该适当地指出,Beta 是一个通用 Windows 库,它可能会对此产生影响。
【解决方案4】:

正如 cmets 中的其他人所说,您正在做的事情是不可能的......您不能在完全相同的命名空间中拥有两个具有完全相同名称的类(除非它们是部分类)。

你有几个选择:

  1. 使用不同的命名空间

好处是您没有这些冲突,并且可以通过完全限定的命名空间指定您的类实例。

阿尔法:

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

测试版:

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

伽玛:

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

这里,omicron 会使用 gamma 作为

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

  1. 使用分部

Partial Class Documentation

当编译器将两个类融合在一起时,您必须合并项目并更改方法名称以避免冲突。怀疑这会按照你想要的方式工作......

阿尔法:

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

测试版:

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

伽玛:

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

  1. 使用策略模式(或带有接口而不是具体类的状态模式)

在这里,您将确定要使用 BaseClass 的哪个扩展(Alpha、Beta 或 Gamma),并通过依赖注入或某种逻辑确定将其替换掉。

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

【讨论】:

    猜你喜欢
    • 2020-05-14
    • 1970-01-01
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    相关资源
    最近更新 更多