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:
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'.
InnerOne
because clients are not allowed to use this level.A(string val)
and A(AInner inner)
constructors.Possible Workarounds:
[assembly: InternalsVisibleTo("OneClient")]
- This is unacceptable due to other classes internals that the specific client needs to use.A(string val)
constructor to A(string val, bool unique=true)
and use it A testA = new A(myString, true)
- Not a nice solution.A()
and call testA.Val = myString;
- This is actually OK but to much code.A testA = new A(myString)
to A testA = new A(val:myString);
- This is actually the chosen solution.Question
Why does this ambiguity happen?
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
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 };
}