Juv
Juv

Reputation: 874

C# Constructors Ambiguity with explicit call - Error CS0012

There is an unexplained ambiguity in C#, where I explicitly try to call a constructor but the compiler thinks it is a different constructor. I will start with showing a short C# architecture we use. Then show a small "working" example I created, and the possible solution to this, but still I like to understand why this happens.

The Architecture:

enter image description here

  1. CLR DLL which bridges the C++ API.
  2. C# API which uses the bridge level.
  3. C# Client applications that use the C# API.

Example I created

A class in the CLR DLL:

#pragma once

#include <string>

using namespace System;

namespace Inner {
    public ref class AInner
    {
    public:

        AInner() : _data(new std::wstring(L"")) {}

        ~AInner() {
            delete _data;
        }

        property String^ Val
        {
            String^ get()
            {
                return gcnew String((*_data).data());
            }

            void set(String^ value) {
                System::IntPtr pVal = System::Runtime::InteropServices::Marshal::StringToHGlobalUni(value);
                *_data = (const wchar_t*)pVal.ToPointer();
                System::Runtime::InteropServices::Marshal::FreeHGlobal(pVal);
            }
        }

    private:

        std::wstring* _data;
    };
}

Class wrapping the CLR level, in a DLL:

using System;

using Inner;

namespace Outer
{
    public class A
    {
        public A()
        {
            _inner.Val = String.Empty;
        }

        public A(string val)
        {
            init(val);
        }

        public string Val
        {
            get
            {
                return _inner.Val;
            }
            set
            {
                _inner.Val = value;
            }
        }

        internal A(AInner inner)
        {
            _inner = inner;
        }

        private void init(string Val)
        {
            _inner = new AInner();
            _inner.Val = String.Empty;
        }

        private AInner _inner;
    }
}

Note that there is an internal constructor and a public constructor.

Executable Client using the C# API DLL:

using Outer;

namespace OneClient
{
    class Program
    {
        static void Main(string[] args)
        {
            string myString = "Some String";
            A testA = new A(myString);
        }
    }
}

Twist in the story:

In the DLL wrapping the CLR level, not ALL API should be used by external clients, but can be used by internal clients, thus the internals are exposed to the internal clients by adding [assembly: InternalsVisibleTo("OneClient")] to the 'AssemblyInfo.cs' of the DLL wrapping the CLR level.

The issue

When compiling the Client code I get the following error: error CS0012: The type 'AInner' is defined in an assembly that is not referenced. You must add a reference to assembly 'InnerOne, Version=1.0.7600.28169, Culture=neutral, PublicKeyToken=null'.

  1. I cannot use InnerOne because clients are not allowed to use this level.
  2. The client is exposed to both A(string val) and A(AInner inner) constructors.

Possible Workarounds:

  1. Remove the [assembly: InternalsVisibleTo("OneClient")] - This is unacceptable due to other classes internals that the specific client needs to use.
  2. Change the A(string val) constructor to A(string val, bool unique=true) and use it A testA = new A(myString, true) - Not a nice solution.
  3. Use default constructor A() and call testA.Val = myString; - This is actually OK but to much code.
  4. Change the client code from A testA = new A(myString) to A testA = new A(val:myString); - This is actually the chosen solution.

Question

Why does this ambiguity happen?

Is this a bug in Microsoft compiler?

Example Sources: Source Code One.zip

Upvotes: 2

Views: 125

Answers (2)

Johnathan Barclay
Johnathan Barclay

Reputation: 20353

Why does this ambiguity happen?

Because to satisfy the constructor overload resolution, the compiler needs to know what all the argument types are, and it doesn't know what an AInner is.

Why not expose the AInner version as a factory method:

static internal A Create(AInner inner)
{
    return new A { _inner = inner };
}

Upvotes: 2

Leandro Bardelli
Leandro Bardelli

Reputation: 11578

I don't see any issue in this, the problem is we are used to do the things in a wrong/briefly way.

The correct answer fot this is:

A testA = new A(val:myString);

Furthermore, all your calls (in this way is a call to a constructor/initializer but it's a call anyway) should be with the parameter name. No one (even me) writes them, but...

Upvotes: 1

Related Questions