this should compile but doesn't

N

not_a_commie

Can anyone tell me what's wrong with the following code? It doesn't
compile. The compiler tries to use the wrong overload. Is it a known
bug? Thanks for your time.


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace BadOverloads
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
Inherited = false, AllowMultiple = false)]
public abstract class XmlFormatterAttribute : Attribute
{
public abstract string Format(object value);

public abstract object Unformat(string value);
}

public class Program
{

public void func(XmlDocument document, XmlFormatterAttribute
attribute)
{
Console.Out.WriteLine("func1");
}

public void func(XmlDocument document, ref object value)
{
Console.Out.WriteLine("func2:" + value.ToString());
}

static void Main(string[] args)
{
//object testobj = new object();
//func(new XmlDocument(), ref testobj); // this doesn't work either

string str = "howdy";
func(new XmlDocument(), ref str);
}
}
}
 
N

not_a_commie

In the example code I posted, the two func overloads should be static.
That doesn't fix the problem, though. I find it fascinating that you
get the same error whether or not they're static.
 
P

Peter Duniho

Can anyone tell me what's wrong with the following code? It doesn't
compile. The compiler tries to use the wrong overload. Is it a known
bug? Thanks for your time.

I don't think this has anything to do with the abstract class or the
overloading. As such, an appropriate, minimal code sample would not
include them. Please try to post minimal code examples, as otherwise
there's a lot of other crud distracting from the issue.

As far as the actual question goes, if you use "ref" or "out" I believe
that the type has to match exactly. I presume that this is because if the
types don't match, an implicit cast has to be made somewhere, but the
parameter being a reference to some existing variable would not be cast in
a context where it's simple for the compiler to know it needs to be cast
(i.e. it would be a run-time thing, too late for the compiler to include
the implicit cast).

Pete
 
J

Jon Skeet [C# MVP]

not_a_commie said:
Can anyone tell me what's wrong with the following code? It doesn't
compile. The compiler tries to use the wrong overload. Is it a known
bug? Thanks for your time.

It shouldn't compile, even if func is static.

The problem is that you're trying to use "ref str" as the argument for
a parameter which is declared as "ref object". You can't do that,
because the method could do:

value = new object();

at which point you'd be stuffed.

I suspect you don't quite understand ref parameters. See
http://pobox.com/~skeet/csharp/parameters.html
 
P

Peter Duniho

The problem is that you're trying to use "ref str" as the argument for
a parameter which is declared as "ref object". You can't do that,
because the method could do:

value = new object();

at which point you'd be stuffed.

I suspect you don't quite understand ref parameters. See
http://pobox.com/~skeet/csharp/parameters.html

For what it's worth, I feel like I understand by-value and by-reference
very well, and I still had to think about the question for a little bit,
and I still have to do some hand-waving to explain why C# doesn't support
the scenario.

In particular, take your example "value = new object()" when the parameter
passed was actually a string. C# _could_ allow for including type
information for the original reference when passing by reference, doing a
run-time cast on any assignments and throwing an exception if the cast
wasn't successful. More complicated? Yes...a lot more complicated. But
I don't see anything that fundamentally prevents that scenario.

Similarly, going the other way -- passing a "ref object" when the method
is declared "ref string" -- the compiler could verify that the "object"
typed variable held a string before the call, and allow that if it was.
Even easier, for the "out" keyword, the compiler could just not care,
since the rules require the parameter to be assigned in the method before
use anyway (so as long as the passed in variable statically typed to
something that could hold the method's parameter type, that should be
okay).

Now, my understanding is that none of this happens. But it could.

In other words, the restriction that when passing parameters using "ref"
or "out" the passed variable type needs to match exactly the parameter
type is somewhat arbitrary. Worse, it is not documented _at all_. I
agree that the restriction can be inferred by thinking about the
consequences of not having it, but there's nothing in any of the
documentation I looked at (this includes MSDN's doc pages for the "ref"
and "out" keywords, your "parameters.html" web page, and the two pages you
refer to in that page) that explicitly states the documentation.

Pete
 
M

Marc Gravell

Yes...a lot more complicated. But
I don't see anything that fundamentally prevents that scenario.

I actually appriate the choice of "keep it simple". For more
complicated scenarios the caller has the capacity (in the object case)
of simply declaring an object variable locally for the duration of the
call, i.e.

string s = "abc";
object obj = s; // perhaps replace with some other base-class
SomeMethod(ref obj);
s = (string) obj; // cross fingers...

Not as succinct, but simple to understand (IMO), plus it can only
break at a point known to the author (which is reasonable, as they are
the ones forcing a square peg into a round hole) - where-as silent
internal casts could start breaking randomly if the SomeMethod author
changes the implementation.

Marc
 
J

Jon Skeet [C# MVP]

Now, my understanding is that none of this happens. But it could.

Well, possibly. The runtime would have to pass the type of the variable
instead of just a pointer etc...
In other words, the restriction that when passing parameters using "ref"
or "out" the passed variable type needs to match exactly the parameter
type is somewhat arbitrary. Worse, it is not documented _at all_.

It is - it's in the C# language specification.
I agree that the restriction can be inferred by thinking about the
consequences of not having it, but there's nothing in any of the
documentation I looked at (this includes MSDN's doc pages for the "ref"
and "out" keywords, your "parameters.html" web page, and the two pages you
refer to in that page) that explicitly states the documentation.

Here's a quote from the C# spec, section 14.4.2.1:

<quote>
For each argument in A, the parameter passing mode of the argument
(i.e., value, ref, or out) is identical to the parameter passing mode
of the corresponding parameter, and

for a value parameter or a parameter array, an implicit conversion
(§13.1) exists from the type of the argument to the type of the
corresponding parameter, or

for a ref or out parameter, the type of the argument is identical to
the type of the corresponding parameter.
</quote>

Unless you meant that the reasons aren't explicitly documented - in
which case I agree. From memory, it's likely to be in the C# Annotated
Standard which is out soon:

http://www.amazon.com/C-Annotated-Standard-Jon-
Jagger/dp/0123725119/ref=sr_1_1/103-6108362-5713441?ie=UTF8
&s=books&qid=1183704314&sr=8-1
 
P

Peter Duniho

I actually appriate the choice of "keep it simple".

I do too. I certainly did not mean to imply that I disagree with the
design.

I'm just pointing out that this is actually not a very well-documented
aspect of the language, IMHO. While, as I said, one can infer the
behavior by considering the implications of the compiler allowing what the
OP thinks should be allowed, it's definitely not explicitly stated
anywhere I looked.

Pete
 
P

Peter Duniho

It is - it's in the C# language specification.

Okay, perhaps "at all" was over-reaching.

However, most people don't read the spec, they read the user reference
materials on MSDN. And I do think it's worth pointing out that your own
page on parameters, which is otherwise a good read, doesn't address this
at all. Not directly (though as I pointed out, one can infer the rules
based on the basic behavior of "ref" and "out", at least if some basic
assumptions are made).

It's my opinion that the MSDN pages that document the "ref" and "out"
keywords should both specifically say that there's a requirement that the
types match exactly. For bonus points, the page discussing "Passing
Parameters (C#)" would also mention that requirement.

Pete
 
J

Jon Skeet [C# MVP]

Okay, perhaps "at all" was over-reaching.

However, most people don't read the spec, they read the user reference
materials on MSDN. And I do think it's worth pointing out that your own
page on parameters, which is otherwise a good read, doesn't address this
at all. Not directly (though as I pointed out, one can infer the rules
based on the basic behavior of "ref" and "out", at least if some basic
assumptions are made).

Righto - I can fix that easily enough. You're right - it's certainly
an omission.
It's my opinion that the MSDN pages that document the "ref" and "out"
keywords should both specifically say that there's a requirement that the
types match exactly. For bonus points, the page discussing "Passing
Parameters (C#)" would also mention that requirement.

Can't fix that :( However, you could certainly mail the MSDN
maintainers - click on the link at the bottom of the page and give the
appropriate feedback. It's worked for me a couple of times.

Jon
 
C

Christof Nordiek

Peter Duniho said:
Okay, perhaps "at all" was over-reaching.

However, most people don't read the spec, they read the user reference
materials on MSDN. And I do think it's worth pointing out that your own
page on parameters, which is otherwise a good read, doesn't address this
at all. Not directly (though as I pointed out, one can infer the rules
based on the basic behavior of "ref" and "out", at least if some basic
assumptions are made).

Actually I searched the programmers reference in MSDN for this issue, and I
didn't even find anything about overload resolution and wich types can be
used in a method call. That docs are sometimes are very poor, regarding such
definitions. But I agree it should be stated there, atleast in a way, that
the most important implication can be induced by it very intuitivly.

Christof
 
N

not_a_commie

Now, my understanding is that none of this happens. But it could.

Thanks Pete. I like you're thinking on this. My $0.02 on making the
compiler handle this: if you can justify immutable variable types
behind the scenes, you can justify autocasting in this case as well.
 
N

not_a_commie

string s = "abc";
object obj = s; // perhaps replace with some other base-class
SomeMethod(ref obj);
s = (string) obj; // cross fingers...

So is the last line of that strictly necessary or only in the case of
value types?
 
N

not_a_commie

Actually I searched the programmers reference in MSDN for this issue, and I
didn't even find anything about overload resolution and wich types can be
used in a method call. That docs are sometimes are very poor, regarding such
definitions. But I agree it should be stated there, atleast in a way, that
the most important implication can be induced by it very intuitivly.

I think it should be stated clearly in the error message. It appears,
though, that the compiler attempts to use the overloads, though, even
though they aren't a close match, thus obfuscating the original error.
This is particularly confusing for the users.
 
J

Jon Skeet [C# MVP]

So is the last line of that strictly necessary or only in the case of
value types?

It's necessary if you want to use the "s" variable to hold the new
value. (And string isn't a value type.)

Jon
 
J

Jon Skeet [C# MVP]

Thanks Pete. I like you're thinking on this. My $0.02 on making the
compiler handle this: if you can justify immutable variable types
behind the scenes, you can justify autocasting in this case as well.

But the method being called doesn't know (normally) what the type of
the original variable is - that's part of the problem.

It could cause some very odd-looking bugs, with an assignment which
looks like it should just work failing at runtime.

Jon
 
P

Peter Duniho

I think it should be stated clearly in the error message. It appears,
though, that the compiler attempts to use the overloads, though, even
though they aren't a close match, thus obfuscating the original error.
This is particularly confusing for the users.

The compiler will mention "overload" even if there's only one version of
the method. I'd agree this is misleading, but it's not unique to this
particular situation.

I have, by now, been trained to just ignore a mention of "overload", and
look at the second error generated (they almost always come in pairs :) ).

Pete
 
P

Peter Duniho

Thanks Pete. I like you're thinking on this. My $0.02 on making the
compiler handle this: if you can justify immutable variable types
behind the scenes, you can justify autocasting in this case as well.

Well, just to be clear: "my thinking" in this context (that is, the post
to which you replied) isn't about what I think _should_ happen. It's
about what I think _could_ happen.

There would be a lot more overhead involved in handling the type coercion
in the way that I describe, since type information would have to be passed
along with the variable itself, and it would introduce new ways for
unexpected runtime errors to occurs.

I don't feel that any of this is warranted, since there's an easy
workaround to the issue: just make sure the types match, and do any
necessary type-casting explicitly as needed. IMHO, that provides a
more-consistent "compiler experience", and is likely to reduce bugs in the
code as well, since everything has to be spelled out explicitly.

I'm not sure what you mean by "immutable variable types behind the
scenes". I'm not aware of any "behind the scenes immutable variables
types". But whatever you mean by that, I'm not sure that it leads to a
logical conclusion that autocasting by-reference parameters is justified.

Pete
 

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