使用重载的构造函数

时间:2015-04-30 14:26:57

标签: c# .net

我最近偶然发现了一个我无法解释的奇怪问题,如果有人能澄清其原因,我会很高兴。

我遇到的问题如下:

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

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

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

谢谢,

巴拉。

2 个答案:

答案 0 :(得分:6)

命名空间依赖问题。错误信息非常简单地说:你的TheMainObject依赖于InterfaceOne,必须正确引用

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

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

  1. 检查所有具有相同名称和相同参数的方法,以查看是否引用了所有参数类型
  2. 然后选择一个匹配调用者参数类型的方法(显式或隐式)。
  3. 我们可以使用以下代码验证步骤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

答案 1 :(得分:1)

有关我遇到的类似问题的解释,请参阅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以避免意外访问动态类型的无效(不存在)成员。