14

How is it possible for no overload of ToString to take zero arguments? The zero-argument ToString is part of System.Object!

Edit in response to the close vote: Since I'm not in a position to upgrade my build server to .NET 4.5, is there any way to make this code work with the .NET 4.0/VS 2010 compilers? Short of giving my overload a completely different name, which is just not cool.

  • The object in question is an F# Discriminated Union that overrides the ToString method inherited from System.Object.
  • The overridden ToString is being called by code in a C# project that's part of the same solution.
  • This all worked fine until I added an extra overload of ToString to my discriminated union, which takes one argument.
  • It all builds and runs perfectly on my local machine (VS 2012, all projects targeting .NET 4.0)
  • It fails on the build server (.NET 4.0) - the C# compiler error in the title showed up as soon as I added the 1-argument overload of ToString to my discriminated union.

I guess the simplest workaround would be to rename my ToString overload to something else, but this is bizarre.

Here's a simple reproduction. You can download a zip file of a solution containing this code here: http://dl.dropbox.com/u/1742470/CS1501_Repro.zip

Compiling that solution in VS 2010 will fail with "CS1501: No overload for method 'ToString' takes 0 arguments". Compiling it in VS 2012 will work just fine. In both cases we're targeting .NET Framework 4.

F#

namespace CS1501_Repro.FSharp

open System

[<Serializable>]
type MyDiscriminatedUnion =
    | Foo of int list
    | Bar of string
    | Baz of int
    | Fizz of float
    | Buzz of DateTimeOffset

    override this.ToString() =
        "Zero Arguments"

    member this.ToString(lookup:Func<int,string>) =
        "One Argument"

C#

using System;
using CS1501_Repro.FSharp;

namespace CS1501_Repro.CSharp
{
    public class Caller
    {
        private MyDiscriminatedUnion _item;

        public Caller(MyDiscriminatedUnion item)
        {
            _item = item;
        }

        public string DoThing()
        {
            return _item.ToString();
        }

        public string DoOtherThing()
        {
            return _item.ToString(i => i.ToString());
        }
    }
}
Joel Mueller
  • 28,324
  • 9
  • 63
  • 88
  • 5
    Please post some code to reproduce the issue you're experiencing. – Daniel Mann Aug 21 '12 at 21:02
  • 4
    Are you able to reproduce this with a short but complete example (just a disriminated union in F#, and C# code calling it)? – Jon Skeet Aug 21 '12 at 21:03
  • Is it possible you called it `toString` or something like that (some subtly different name)? – Servy Aug 21 '12 at 21:04
  • I'll see if I can make an isolated reproduction - it'll take me a few minutes because I need to fix the broken build first and test my code on two machines to verify that I've reproduced it. – Joel Mueller Aug 21 '12 at 21:04
  • @Servy - Nope. I'm working around this issue by changing it to a subtly different name (ToLabelString). – Joel Mueller Aug 21 '12 at 21:05
  • I've updated with sample code reproducing the problem, and a link to download a solution zip that demonstrates it. – Joel Mueller Aug 21 '12 at 21:47
  • 2
    Interestingly, if you convert the F# project to Visual Studio 2012 and build it there, then the C# project is able to build successfully back in Visual Studio 2010. – mellamokb Aug 21 '12 at 21:55

2 Answers2

6

This is just a partial answer, but I noticed that VS2010 and VS2012 generate different IL for the F# class. If you look at the IL for the ToString(lookup:Func<int,string>) method, then you will see that the output in VS2010 is:

.method public instance string  ToString(class [mscorlib]System.Func`2<int32,string> lookup) cil managed
{
  // Code size       7 (0x7)
  .maxstack  3
  IL_0000:  nop
  IL_0001:  ldstr      "One Argument"
  IL_0006:  ret
} // end of method MyDiscriminatedUnion::ToString

while the output in VS2012 is:

.method public hidebysig instance string 
        ToString(class [mscorlib]System.Func`2<int32,string> lookup) cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "One Argument"
  IL_0006:  ret
} // end of method MyDiscriminatedUnion::ToString

The difference is the hidebysig annotation in the method signature. If hidebysig is added, then the method will hide other implementations (in the base class) that share the same name and signature, while if hidebysig is omitted, it will hide all other implementations with the same name even if the signature is different. This stack overflow question contains a good answer that describes hidebysig.

So while this answer doesn't solve your problem, it explains why it doesn't compile in VS2010.

Community
  • 1
  • 1
Elian Ebbing
  • 18,779
  • 5
  • 48
  • 56
  • Thanks for the information on `hidebysig` - it definitely sounds like that's the issue. I guess it's a work-around for me, at least until I get the go-ahead to update the compilers on the build server. – Joel Mueller Aug 22 '12 at 00:00
5

I have to admit, this is the first time I have ever used F#, so this solution is probably not the most elegant, however, you mentioned that ToString() is part of System.Object...

I solved this issue by adding a line of code into the DoThing() method which assigns _item to object, and then calls ToString() from the object, giving, in this instance, the desired result:

F# code (unchanged)

type MyDiscriminatedUnion =
    | Foo of int list
    | Bar of string
    | Baz of int
    | Fizz of float
    | Buzz of DateTimeOffset

    override this.ToString() = "Zero Arguments"
    member this.ToString(lookup:Func<int,string>) = "One Argument"

C# code (see change in DoThing() method)

    public class Caller
    {
        private Module1.MyDiscriminatedUnion _item;

        public Caller(Module1.MyDiscriminatedUnion item)
        {
            _item = item;
        }

        public string DoThing()
        {
            object result = _item; //assign to object
            return result.ToString(); //call ToString() on object
        }

        public string DoOtherThing()
        {
            return _item.ToString(i => i.ToString());
        }
    }

I do apologize if this is not what you want...but it's all I can think of, and if anyone thinks it's the wrong way to go about it, please do say why!

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 1
    That's a good thought, casting to object. As workarounds go, I think I prefer just giving my overload a different name than ToString, but there's certainly nothing wrong with this approach. – Joel Mueller Aug 21 '12 at 23:55
  • Wouldn't a cast work just as well? `((object)_item).ToString()` – Daniel Aug 22 '12 at 02:37