2015-04-30 3 views
5

Я недавно наткнулся на нечетную проблему, которую я не мог объяснить, и я был бы рад, если бы кто-нибудь мог прояснить, почему это происходит.Ошибка создания экземпляра объекта при использовании перегруженного конструктора

Проблема я столкнулся заключается в следующем:

У меня есть интерфейс, который реализуется, например, так:

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

Как ни странно, этот код не будет компилироваться со следующей ошибкой:

The type 'InterfaceOne.IB' is defined in an assembly that is not referenced.
You must add a reference to assembly '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

Я также что если я изменю одну из перегрузок, чтобы содержать дополнительный параметр - он компилируется ... Что заставило меня думать, что проблема может быть связана с какой-то проблемой отражения, с которой работает компилятор.

Thanks,

Barak.

+1

щелкните правой кнопкой мыши проект и убедитесь, что сборка правильно указана путем добавления ссылки на файл-> перестроить и посмотреть, что произойдет. – maximdumont

+1

Вам необходимо добавить ссылку в свой исследовательский проект для визуальных студий (в рамках тестового проекта) в файл сборки, созданный от интерфейсаOne. Смотрите здесь: https://msdn.microsoft.com/en-us/library/7314433t%28v=vs.90%29.aspx – ZivS

+0

Это не проблема ссылок, поскольку я не хочу, чтобы ReferenceTest знал о интерфейс IB или класс B. Я также упомянул об этом: «Я также обнаружил, что если я изменю одну из перегрузок, чтобы добавить дополнительный параметр - он компилируется ...». Я предполагаю, что проблема связана с тем, что одна из перегрузок с одинаковым количеством параметров имеет IB в ней, и поэтому при компиляции ей также нужно знать IB. – BarakH

ответ

0

См this для объяснения подобной проблемы, которые я столкнулся. Цитирую ответ по ссылке:

The C# standard specifies that overload resolution (section 7.5.3) is performed by comparing each matching signature to determine which is a better fit. It doesn't say what happens when a reference is missing, so we must infer that it still needs to compare those unreferenced types.

В вашем примере, это должно быть очевидно, что перегружать вы используете, но компилятор не достаточно умна, и по-прежнему будет пытаться сравнить оба перегрузкам, поэтому и требуются ссылки.

Возможно, самое легкое, но не самое приятное решение (если вы не хотите включать недостающую ссылку, к которой у вас может быть причина не относится) заключается в добавлении дополнительного параметра фиктивного эффекта, что делает его очевидным для компилятор, который перегружает вас; или преобразование двух конструкторов 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 во избежание случайного доступа к недействительным (несуществующим) членам динамического типа.

+0

Так же, как и я, большое спасибо! Еще одно прекрасное решение (мой друг предложил) - создать родительский интерфейс для обоих, что будет немного более эффективным в отношении переданного типа. Но это только в том случае, если оба типа имеют общую основу, и оба типа доступны для редактирования. Поскольку в моем случае я не хотел редактировать эти интерфейсы, я решил использовать «Объект» в качестве типа параметра, а затем безопасно попытаться выполнить его. Думаю, и с вашим решением я пойду посмотреть, какие плюсы и минусы будут в использовании «динамического» вместо передачи объекта. Еще раз спасибо! – BarakH

6

проблема зависимости пространства имен. Сообщение об ошибке довольно месиво сказал, что: ваш TheMainObject зависит от InterfaceOne и должны быть надлежащим образом ссылаться

это не имеет прямого отношения к конструктору перегрузки ...

Update: Это скорее поведение компилятора , Для того, чтобы определить, какой перегруженный метод использовать, компилятор должен

  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

+0

Я рассмотрел этот вопрос выше, и я не могу его копировать здесь ... Пожалуйста, прочитайте мой комментарий к ответам ZivS и Ravingheaven. – BarakH

+0

Я думаю, что это поведение компилятора. когда вы вызываете TheMainObject (a), компилятор проверяет все конструкторы с одним параметром, а затем находит соответствие по типу. Возможно, поэтому, когда вы обновляете конструктор до TheMainObject (IB iaObj, int x), ошибка компиляции отсутствует. Но если вы делаете второй параметр необязательным: TheMainObject (IB iaObj, int x = 0), ошибка возвращается снова. – Tzu

+0

Кстати, вы можете использовать отражение, чтобы избежать этой контрольной проверки во время компиляции.но вы должны быть осторожны, если вы это сделаете: var a = new A(); TheMainObject theMainObject = null; var ctor = typeof (TheMainObject) .GetConstructor (новый [] {typeof (IA)}); if (ctor! = Null) theMainObject = (TheMainObject) ctor.Invoke (новый объект [] {a}); – Tzu

Смежные вопросы