Internal constructor "visible" outside of assembly. Compiler bug?

M

Martin Oddman

Hi,

I have a compiling problem. Please take a look at the code below.
I have an application that is built upon three tiers: one data tier
(Foo.DataManager), one business tier (Foo.Kernel) and one web
presentation tier (Foo.WebFiles). The data tier shall only be
accessible thru the business tier so I do NOT want a reference to
the data tier in the presentation tier.
In the business tier I have a class with the name CategoryItem that
have one public constructor, that shall be able to instantiate from
the presentation tier, and one internal constructor that shall only
be instantiated from within the same assembly. The internal
constructor have an argument with a reference to a class,
TransactionHandler, located in the data tier. And here is where the
problems begin. When I add a constructor like this in the
CategoryItem class and then try to compile the ActionMenu class I get
the following compiling error:

error CS0012: The type 'Foo.DataManager.TransactionHandler' is
defined in an assembly that is not referenced. You must add a
reference to assembly 'Foo.WMS.DataManager'.

I really do not understand why I get this error, cause as I see it,
this shall not be any problem at all because the constructor with a
reference to the Foo.DataManager is INTERNAL and shall not even be
visible for the presentation tier. Is this a bug in the compiler or
have I missed something here? And, how do I come around this problem?
As I said before I realy do NOT want a reference from the
presentation tier to the data tier.

By the way, it doesn't matter if I set this constructor to be
PROTECTED or PRIVATE, I still get the same compiling error.

I appreciate any help. Thank you in advance.

Best regards
Martin


/*** First class located in Foo.Datamanager.dll ********************/

namespace Foo.DataManager {
public class TransactionHandler {
public TransactionHandler(){
}
}
}

/*** Second class located in Foo.Kernel.dll that has ***************/
/*** a reference to the Foo.Datamanager.dll above. ***************/

using Foo.DataManager;

namespace Foo.Kernel {

public class CategoryItem {

public CategoryItem(){
}

internal CategoryItem(TransactionHandler transaction) {
}

}
}

/*** Another class with NO reference to Foo.Datamanager.dll *******/
/*** but that wants to instantiate Foo.Kernel.CategoryItem *******/

using Foo.Kernel;

namespace Foo.WebFiles {
public class ActionMenu : System.Web.UI.Page {
private void AddItem(){
CategoryItem category = new CategoryItem();
}
}
}

/*******************************************************************/
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Hi Martin,

IMHO your problem is somewhere else. I compiled the code you've posted and
it compiles ok. You shouldn't have any problems compiling even if the
constructor was public as long as you don't use it. This error will happen
if you call a method or access a property in you presentation tier that has
parameter or return value of type TransactionHandler.

Let say for a moment that in your CategoryItem you have

public TransactionHandler Foo()
{
return...;
}

Even with this method you have to be able to compile.
But if somewhere in your presentation tier you have

....
Foo();
....

even though you don't use the return value you'll get that error.

So, check you code. I think you've missed something.
 
M

Martin Oddman

I have made some research on this and found out an obvious bug in
the compiler.
If you have one internal constructor and one public constructor with
the same number of arguments and the internal constructor have an
argument with a reference to a class that shall not be accessible
for classes that calls the public constructor then you get this
error.
I hadn't noticed that in my first mail so my first example wasn't
100% correct. Here is an exact exempel how to reproduce it:

/*******************************************************************/
/*** Create a project called Foo.Datamanager. ***/
/*** Create a file in that project named TransactionHandler.cs ***/
/*** and paste this code into that file. ***/
/*******************************************************************/

namespace Foo.DataManager {
public class TransactionHandler {
public TransactionHandler(){
}
}
}

/*******************************************************************/
/*** Create a project called Foo.Kernel. ***/
/*** Create a file in that project named CategoryItem.cs ***/
/*** and paste this code into that file. ***/
/*** Then make a reference from this project to Foo.Datamanager. ***/
/*******************************************************************/

using Foo.DataManager;

namespace Foo.Kernel {

public class CategoryItem {

public CategoryItem(){
}

public CategoryItem(string s){
}

internal CategoryItem(TransactionHandler transaction) {
}

}
}

/*******************************************************************/
/*** Create a project called Foo.UserInterface. ***/
/*** Create a file in that project named UserInterface.cs ***/
/*** and paste this code into that file. ***/
/*** Then make a reference from this project to Foo.Kernel. ***/
/*******************************************************************/

using Foo.Kernel;

namespace Foo.UserInterface {
public class GUI {
public void AddItem(){
CategoryItem category = new CategoryItem(s);
}
}
}

/*******************************************************************/
/*******************************************************************/
/*******************************************************************/

If I remove either the middle constructor or the last constructor in
CategoryItem it works.
If I put an extra dummy argument on the internal constructor, so the
internal and public constructor get different number of arguments it
also works.
And one other funny thing is that it also works as long as you just
use the default constructor and not the CategoryItem(s) constructor
from the UserInterface class...
But as soon as the public and the internal constructors has the same
numbers of arguments I get this error message when trying to compile:

error CS0012: The type 'Foo.DataManager.TransactionHandler' is
defined in an assembly that is not referenced. You must add a
reference to assembly 'Foo.DataManager'.

It doesn't even matter what type the argument of the public
constructor has.

I can't see this is anything than a bug in the compiler.

/Martin
 
M

Martin Oddman

I just noticed the problem and that I missed some code in my last example. Read my last post and you will find code that do not compile. We've done 3 differnt projects on 3 different computers with the same result here...
 
M

Martin Oddman

LOL, it does compile in MONO but not in Microsofts own compiler.
Nice work Microsoft!!!

Delenda est Microsoft

/martin...
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Nope, this is not it. Try to compile your own sample. It compiles with and
without the new constructor.
 
R

Raymond Lewallen

I can compile your code posted below with and without the constructor
without any problems.

Raymond Lewallen
Federal Aviation Administration
 
J

JD

In V1.1 C# compiler (not using visual studio just using csc) I did get the
same result as Martin, but I'm not so sure if its a bug or not.

From the C# spec:

<snip>
7.4.2 Overload Resolution
Overload resolution is a compile-time mechanism... (which is clear)
:
:
Once the candidate function members and the argument list have been
identified, the selection of the best function member is the same in all
cases.

Given the set of applicable candidate function members, the best function
member in that set is located. If the set contains only one function member,
then that function member is the best function member. Otherwise, the best
function member is the one function member that is better than all other
function members with respect to the given argument list, provided that each
function member is compared to all other function members using the rules in
§7.4.2.2.

7.4.2.2 Better Function Member
Given an argument list A with a set of argument types { A1, A2, ..., AN }
and two applicable function members MP and MQ with parameter types { P1, P2,
...., PN } and { Q1, Q2, ..., QN }, MP is defined to be a better function
member than MQ if

For each argument, the implicit conversion from AX to PX is not worse than
the implicit conversion from AX to QX, and

For at least one argument, the conversion from AX to PX is better than the
conversion from AX to QX.

When performing this evaluation, if MP or MQ is applicable in its expanded
form, then PX or QX refers to a parameter in the expanded form of the
parameter list.

</snip>

I believe that private/internal does not influence overloading
determination. If this is true then the only way the compiler knows how to
resolve the two constructors is by going through the logic in 7.4.2.2 above.
How would the compiler run through the logic in 7.4.2.2 without knowing what
the TransactionHandler type is? I'm no compiler expert but this problem just
had me thinking. And maybe Mono isn't as strict as it should be.

JD
 
M

Martin Oddman

Have you tried the last example I did send to this group? Or just
the first example?

Write a mail to me at martin[at]dotify[dot]com and I will send
you a c# that definitly do not work here.
I have recreated this problem at three totaly different computers
in 3 totaly different solutions...
 
M

Martin Oddman

Have you tried the last example I did send to this group? Or just
the first example?

Write a mail to me at martin[at]dotify[dot]com and I will send
you a c# that definitly do not work here.
I have recreated this problem at three totaly different computers
in 3 totaly different solutions...
 
J

JD

I apologize, the section I was refering to in the current C# specification
is 14.4.2 Overload Resolution. I recommend you
read that section.

If you try compiling your example with the SSCLI (Rotor) implementation you
get the same error. Looking at the source code
for the compiler, it looks like the function verifyMethodCall gathers the
best methods by number of arguments and then
confirms if the given arguments types are convertible to the method's types.
If it passess then it puts the method on a
"best method" list. If the best list has more than one method it then goes
on to the more complicated best fit by
conversion logic.

In your example it looks like when the number of arguments don't conflict
all is well because it finds the one with the
string. But when it finds the TransactionHandler constructor which matches
the number of arguments, it checks to see if
the conversion is possible and thats when the compiler needs the type and
complains.

Mono on the other hand can resolve the string, it also finds the constructor
with TransactionHandler but because it
doesn't know anything about that type chooses to ignore it as a possible
best fit and chooses the string constructor.

So it looks like MS implementation is find all the methods with the same
number of arguments and then decide by type
conversion. Mono implementation is find all the methods with the same number
of arguments excluding those whose type is
unknown, then decide by type conversion. Reading the C# specification, it
says first find all applicable methods by number
of arguments then apply best fit by type. My view is that MS is doing the
right thing or at least the behavior is "intentional"
and can be explained, whether its a bug or not, I don't think so, but I'm
sure it all can be read different ways.

Just for kicks here is another test that I did to see what happens with a
different inheritence chain. The results were
the same, mono compiles (and ran) and MS doesn't compile.

Base.dll
public class Base {

}

Derived1.dll
//references Base.dll
public class Derived1:Base {

}

Derived2.dll
//references Base.dll
public class Derived2:Base {

}


Intermediate.dll
//references Base.dll and Derived2.dll
public class Intermediate {

public Intermediate(Base B){
Derived2 D2 = B as Derived2;
}

public Intermediate(Derived2 D2){
}

}

Final.dll
//references BAse.dll and Derived1.dll and Intermediate.dll
public class Final {

public Final(){
Intermediate I = new Intermediate(new Derived1());
}

public static void Main()
{

Final F = new Final();
System.Console.Write("went well");
}

}
 
M

Martin Oddman

Thanks JD!

Very nice of you to do all this testing! Very appreciated :)

We were currently also thinking about to try it in Rotor but I guess we do
not need to do that now ;)

I actually have started a Microsoft Support Case about this issue. I still
think this behaviour is a bug in either the compiler or by the design of
the specification. If this won't be fixed it realy screw things up when it
comes to make a nice design of your code...

I will return to you as soon as I hear Microsofts view on this subject.

Best Regards
Martin
 
J

JD

I will return to you as soon as I hear Microsofts view on this subject.

Thanks, I look forward to it and good luck.
 
M

Martin Oddman

We have now confirmed that this code compile in VS 2005 _BETA_. I'm pretty
sure that you use that one. And as a so called Microsoft Valuable Person you
shall no better than to test problems like this in a beta and then assert
distinctly that there is no problem. Most developers still sit with the last
sharp version of the VS environment, especially thouse who work in real
projects...

The fact that this code does compile in 2005 BETA but not in 2003 just prove
that this is a bud in the compilor for C# 1.1

/Martin
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Martin,
And as a so called Microsoft Valuable Person you
shall no better than to test problems like this in a beta and then assert
distinctly that there is no problem.


I use VS 2003 and as I said before I can compile that code. I wouldn't keep
trying to help you based on the results I had gotten using beta products.
I also sent you an email, expecting, as you said, to send me a test project
that don't compile. You haven't send me anything so far.
I really was trying to help.
Good luck with the problem.

BTW "as so called MVP" I still have a square job as a developer and I use
release versions of VS only.
 
M

Martin Oddman

Well, I'm sorry, I probably overreacted, but the fact is that we now have
recived a mail from Microsoft themselfs where they admit that this problem
exists in the compiler for C# 1.1. It will be fixed in upcoming versions
though (VS 2005+)... So if you still can compile the code it is a bit
strange... Will send you the test project right away and you can try with
that...
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Ok, I'll check the code once I receive it. The reason I wanted to test your
project is because it might be that I miss something. I'll post the results
when I get them.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Yup, it doesn't compile.

The reason that it did compile in my previous test is that I added the new
constructor (with string parameter), but I was still creating the object
using the parameterless one. I missed to see that little detail in your
sample.

Well for now you have workaround. For example you can create BusinessManager
using reflection and wait for the next version of the compiler.

Type t = typeof(BusinessTier.BusinessManager);
ConstructorInfo ci = t.GetConstructor(new Type[]{typeof(int)});
BusinessTier.BusinessManager m = ci.Invoke(new object[]{0}) as
BusinessTier.BusinessManager;

Or use the defalut constructor if you have issues with the security
permissions and set the parmaters via the class' properties.

--

Stoitcho Goutsev (100) [C# MVP]


Stoitcho Goutsev (100) said:
Ok, I'll check the code once I receive it. The reason I wanted to test your
project is because it might be that I miss something. I'll post the results
when I get them.

--

Stoitcho Goutsev (100) [C# MVP]


Martin Oddman said:
Well, I'm sorry, I probably overreacted, but the fact is that we now have
recived a mail from Microsoft themselfs where they admit that this problem
exists in the compiler for C# 1.1. It will be fixed in upcoming versions
though (VS 2005+)... So if you still can compile the code it is a bit
strange... Will send you the test project right away and you can try with
that...
 
M

Martin Oddman

Here is the first mail I got from Microsoft Support:

<quote>

Hi Martin,

I have now done some research and found out that the behaviour you are
seeing is reported as a problem.

However, the C# compiler team hasn't accepted that it is a bug in the
current version.

The reasoning behind this is that the C# language spec does not
specify what the behavior should be. However, they have stated that
the behavior is not what they want and will be changing it for the
next version.

I realize that that doesn't help the situation that you are in at the
moment.

I will investigate options to move forward and then get back to you.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top