Search code examples
c#constructorclr

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.
  • Note that the C# Clients are not allowed to use the CLR level.

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?

  • I call the A(string val) with the myString which is actually a string value This is very strange.

Is this a bug in Microsoft compiler?

Example Sources: Source Code One.zip


Solution

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