【问题标题】:Object instantiation fails when using overloaded constructor使用重载构造函数时对象实例化失败
【发布时间】:2015-04-30 14:26:57
【问题描述】:

我最近偶然发现了一个我无法解释的奇怪问题,如果有人能解释为什么会发生这种情况,我会很高兴。

我遇到的问题如下:

我有一个已实现的接口,如下所示:

namespace InterfaceTwo
{
    public interface IA { }
}

namespace InterfaceTwo
{
    public class A : IA { }
}

还有另一个在不同项目中实现的接口,如下所示:

namespace InterfaceOne
{
    public interface IB { }
}

namespace InterfaceOne
{
    public class B : IB { }
}

我有一个在其构造函数中使用这些接口的对象,如下所示:

using InterfaceOne;
using InterfaceTwo;

namespace MainObject
{
    public class TheMainObject
    {
        public TheMainObject(IA iaObj) { }

        public TheMainObject(IB iaObj) { }
    }
}

最后,我有一个聚合上述对象的类,如下所示:

using InterfaceTwo;
using MainObject;

namespace ReferenceTest
{
    public class ReferenceTest
    {
        public void DoSomething()
        {
            var a = new A();
            var theMainObject = new TheMainObject(a);
        }
    }
}

奇怪的是,这段代码无法编译并出现以下错误:

类型“InterfaceOne.IB”在未引用的程序集中定义。
您必须添加对程序集“InterfaceOne,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”的引用。
c:\users\harry.baden\​​documents\visual studio 2013\Projects\ReferenceTest\ReferenceTest\ReferenceTest.cs 11 13 ReferenceTest

我还发现,如果我更改其中一个重载以包含一个额外的参数 - 它确实会编译...是什么让我想到这个问题可能与编译器正在运行的某种反射问题有关。

谢谢,

巴拉克。

【问题讨论】:

  • 右键单击项目并通过添加对文件的引用来确保正确引用程序集->重建并查看会发生什么
  • 您需要在 Visual Studio 解决方案资源管理器中(在您的测试项目下)添加对 interfaceOne 创建的程序集文件的引用。看这里:msdn.microsoft.com/en-us/library/7314433t%28v=vs.90%29.aspx
  • 这不是引用问题,因为我不希望 ReferenceTest 知道接口 IB 或 B 类。我还提到过 - “我还发现,如果我更改其中一个重载以包含一个额外的参数 - 它确实编译......”。我假设问题与具有相同参数数量的重载之一具有 IB 的事实有关,因此,在编译时,它也需要知道 IB。

标签: c# .net


【解决方案1】:

命名空间依赖问题。错误消息非常贴切地说:您的 TheMainObject 依赖于 InterfaceOne 并且必须正确引用

这与构造函数重载没有直接关系......

更新: 它更像是一种编译器行为。为了确定使用哪个重载方法,编译器必须

  1. 检查所有具有相同名称和相同参数#的方法,以查看是否引用了所有参数类型
  2. 然后选择一种与调用方参数类型(显式或隐式)匹配的方法。

我们可以用下面的代码验证第1步和第2步是分开的:

using InterfaceOne;
using InterfaceTwo;
namespace MainObject
{
    public class TheMainObject
    {
        public TheMainObject(IA obj) { }
        public TheMainObject(IB obj, int x) { }
    }
}

using InterfaceTwo;
using MainObject;
namespace ReferenceTest
{
    public class ReferenceTest
    {
        public static void DoSomething()
        {
            var a = new A();
            var theMainObject = new TheMainObject(a); //no error
        }
    }
}

上面的代码编译是因为TheMainObject(IB obj, int x) 不是new TheMainObject(a) 的候选者。但是如果构造函数被定义为

public TheMainObject(IB obj) { }

public TheMainObject(IB obj, int x = 0) { }

需要对 InterfaceTwo.IB 的引用。

您可以通过在运行时调用构造函数来避免这种引用检查,但这容易出错,您应该小心。例如:

public static void DoSomething()
{
    var a = new A(); 
    TheMainObject theMainObject = null; 
    var ctor = typeof (TheMainObject).GetConstructor(new[] {typeof (IA)}); 
    if (ctor != null) {
        theMainObject = (TheMainObject) ctor.Invoke(new object[] {a});
    }
}

我做了更多的研究,发现了以下资源。基本上类型扩大/缩小步骤需要了解所有涉及的类型。 (VB 版本仅供参考,因为 C# 规范适用于 VS.Net 2003)。

Overload Resolution C#

Overload Resolution Visual Basic

【讨论】:

  • 我已经在上面解决了这个问题,但我似乎无法将其复制到这里...请阅读我对 ZivS 和 Ravingheaven 回复的评论。
  • 我认为这是一种编译器行为。当您调用 TheMainObject(a) 时,编译器会使用单个参数检查所有构造函数,然后按类型查找匹配项。也许这就是为什么当您将构造函数更新为 TheMainObject(IB iaObj, int x ) 时,编译错误消失了。但是如果你将第二个参数设为可选:TheMainObject(IB iaObj, int x =0),错误又回来了。
  • 顺便说一句,您可以使用反射来避免在编译时进行此引用检查。但如果你这样做,你应该小心: var a = new A(); TheMainObject theMainObject = null; var ctor = typeof (TheMainObject).GetConstructor(new[] {typeof (IA)}); if (ctor != null) theMainObject = (TheMainObject) ctor.Invoke(new object[] {a});
  • 是的,编译器的行为正是我所想的。您想将其作为评论发布,以便我将其标记为答案吗?
【解决方案2】:

请参阅this,了解我遇到的类似问题的解释。引用链接中的答案:

C# 标准指定通过比较每个匹配的签名来确定哪个更适合来执行重载解决方案(第 7.5.3 节)。它没有说明缺少引用时会发生什么,因此我们必须推断它仍然需要比较那些未引用的类型。

在您的示例中,您使用的重载应该很明显,但是编译器不够聪明,仍然会尝试比较两个重载,这就是需要两个引用的原因。

也许最简单但不是最漂亮的解决方案(如果您不想包含缺少的参考 - 您可能有充分的理由不这样做)是添加一个额外的虚拟参数,有效地使其对您正在调用的重载编译器;或将两个 TheMainObject 构造函数转换为具有不同名称的两个方法,例如TheMainObjectA(IA iaObj)TheMainObjectB(IB ibObj) - 即完全避免重载。

另一种可能的解决方案是使用 dynamic 关键字(适用于 .NET 4.0 及更高版本),尽管有些人可能不鼓励这样做,因为如果你不小心它可能会导致运行时错误:

public class TheMainObject
{
    public TheMainObject(dynamic obj)
    {
        if (obj is IA)
        {
            // work with IA ...
        }
        else if (obj is IB)
        {
            // work with IB ...
        }
        else
        {
            // exception ...
        }
    }
}

这样,编译器不会生成错误,因为 obj 参数是在运行时评估的 - 您的原始代码将起作用。如果您选择使用此解决方案,还请考虑检查 RuntimeBinderException 以避免意外访问动态类型的无效(不存在)成员。

【讨论】:

  • 和我一样,非常感谢!另一个很好的解决方案(我的朋友建议)是为两者创建一个父接口,这对于传递的类型将更加强制。但是,只有当这两种类型都有一些共同点并且两种类型都可用于编辑时。因为,就我而言,我不想编辑这些接口,所以我决定使用“Object”作为参数类型,然后安全地尝试转换它。我想你的解决方案也将看看使用“动态”而不是传递对象有什么优点和缺点。再次感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-05
  • 1970-01-01
  • 1970-01-01
  • 2015-03-25
相关资源
最近更新 更多