views:

1175

answers:

10

I have a class that contains a bunch of properties. It is a mistake by a programmer if they call ToString() on an object of that type. Take this example code:

using System;

public class Foo
{
    public int ID = 123;
    public string Name = "SomeName";

    private string ToString() { return null; }
}

public class MyClass
{
    public static void Main()
    {
     Foo myObj = new Foo();
     WL("I want this to be a compiler error: {0}", myObj.ToString());
     RL();
    }

    #region Helper methods

    private static void WL(object text, params object[] args)
    {
     Console.WriteLine(text.ToString(), args); 
    }

    private static void RL()
    {
     Console.ReadLine(); 
    }

    #endregion
}

You could reason that if ID is what most people want written out as a string, then I should implement ToString so that it returns the ID. However, I believe that is a bad practice because programmers will "accidentally" get working code. A programmer using my class should specify what they want.

Instead, what I would like is if someone calls myObj.ToString() to have that show up as a compile time error. I thought I could do that by creating a private ToString() function, but that doesn't work.

The reason I brought this up is that we ended up with a query string that contained the fully qualified class name rather then an ID.

So the question is: Is there any way to "hide" the ToString() function so that calling it on an object of my class causes a compiler error?

+18  A: 

The Obsolete attribute allows you to do this.

[Obsolete("Use the XYZ properties instead of .ToString() on Foobar", true)]

The boolean at the end is for whether the compiler should consider use of this member an error.

Sander
Ehh...this is probably less "hacky" than overriding the ToString() with an incorrect return.
EBGreen
It works, at least. Maybe the .NET folks need to add an "IToldYouNeverToCallMe" attribute.
MusiGenesis
This does work and it's the solution I'm going with unless Jason Jackson provides a good reason why I shouldn't.
It won't stop some one casting foo to an Object and calling tostring on that, or passing it to a generic function that uses to string, or for that matter, passing it to Console.WriteLine or similar. I'm up voting Jason Jackson
Binary Worrier
I actually upvoted Jason and this both. I think Jason is right that it shouldn't be done, but if the OP were determined to do it anyway this is the way I would suggest.
EBGreen
I up voted this too. I haven't had so many people talk about me since that time in college when I ...
Jason Jackson
Obsolete is perfect to avoid the accidental use of ToString. It tells the user "hey, this doesn't do what you think it does".
MichaelGG
+1  A: 

Use the override keyword with a public ToString() function to override the System.Object ToString() methiod.

EBGreen
I thought about this, too, but realized that it's a bad idea because the IDE uses the .ToString() method in a number of places.
Joel Coehoorn
+36  A: 

I cannot stress enough how bad an idea this design is.

ToString() is part of the object contract in .Net. If you don't want to implement it then don't override it, and just let it return the type info. What harm could that possible cause?

I don't mean to be so negative, but I am absolutely floored that someone would want to get rid of ToString().

Some additional points:

  1. Why do the programmers using this class assume that ToString() will return an ID? Are other classes in your ecosystem doing this? One can argue that ToString() should return some meaningful data. But you really shouldn't be programming against the results of a ToString() call. ToString() is for a string representations of the class, period. This sounds like an education or communication issue between programmers or departments.
  2. Crippling ToString() in any way, whether you can figure out how to at compile time or by throwing an exception at run time, will have ripples. I have never seen this done, and would not expect any class I am using to exhibit this behavior. I think most programmers would have the same expectations. Will future programmers that use your class expect this? What bugs and maintenance nightmares are you causing down the road?
  3. What impact does this have in the IDE or debugger, which rely on ToString()?
  4. What impact will this have when using databinding technologies that don't bind against a specific type, but use reflection at run-time to pull out values? Most databinding will fall back to calling ToString() on an object if a member is not specified to use.
Jason Jackson
For what it is worth I agree that the idea is bad practice.
EBGreen
I agree, but his real problem is that users of his class are incorrectly assuming ToString() gives the object's ID. What would be the best solution to that problem?
MusiGenesis
I would ask why they assume ToString() provides an ID? Are there other classes with ToString() returning an ID in the ecosystem? Is this an education/communication problem (not everything should be fixed in code)?
Jason Jackson
1. A redesign _might_ be better, so valid points.2. It doesn't matter what they expect. When do you call ToString (on a specific type) to get the default type info string? It's almost always a mistake.3. No issues; they call the original ToString.4. Ditto; they'll get the type info from obj.ToStr
MichaelGG
+1  A: 

Override ToString to return string.Empty, then you wouldn't have anything appended to the query string. By default if you don't override ToString you'll get Object's version which returns this.GetType() which will give you something like the namespace and class name.

Calling ToString seems a pretty reasonable thing to do, I wouldn't want to raise complier errors for someone doing that.

PhilGriffin
A: 

This is interesting - I would have expected your code to produce a compile time error because Foo.ToString() method in the Foo class is private.

I understand that the Foo.ToString() does not override System.Object.ToString(), it hides the inherited ToString(). However, in your example ToString() is being called on a Foo reference, so I would have thoght that the private access modifier would apply here.

If ToString() were called via a System.Object reference a completely different ToString() method would be invoked. In that case,System.Object.ToString() would be invoked and the type name would be returned.

But why doesn't the C# compiler respect the private access modifier when calling through a Foo reference? Can anyone explain?

Michael Burr
A: 

In Response to Jason Jackson:

I see what you are saying, but I think there is a trade-off involved. On one hand, why can't I control what functions are apart of my class? More importantly, why shouldn't I be able to spot programming errors at compile time rather then run time?

To answer your question about what harm could be caused: well, nothing majors, but it's just more bugs/problems to flesh out during development. It's type info in query strings, sent to the database, etc, where someone mistyped something. Yes, in an ideal world, that would never happen and the code should be fixed and eventually it will.

Can you better explain the "object contract in .Net" that you speak of? What harm can marking ToString as Obsolete possibly cause?

It breaks Liskov's Substitutability Principle
Jon Skeet
Daniel, this is not a forum. The thing you just posted is an answer, but you are actually responding to my post. Please do so in the comments section of my answer, or clarify your question with an edit.
Jason Jackson
To respond to your post, I would first say to read what Jon Skeet posted. It is dead-on correct. The object contract is the idea that your object should adhere to a contract. Your object extends System.Object, it should correctly implement ToString() as part of that contract (or leave it alone).
Jason Jackson
Reading my comments, I don't want you to think I am biting your head off Daniel. I don't mean anything in a negative way.
Jason Jackson
Yea, so just don't override, and instead declare it new (shadow). The OO stuff will still work, and anyone using his object type will get a compile error...
MichaelGG
+5  A: 

I completely disagree with using the Obsolete property for this for a few reasons.

To start with you will now have a warning for the ToString() method that you overrode and tagged with the Obsolete property:

    [Obsolete("dont' use", true)]
    public override string ToString()
    {
        throw new Exception("don't use");
    }

yields this warning: Warning 1 Obsolete member 'ClassLibrary1.Foo.ToString()' overrides non-obsolete member 'object.ToString()' d:\source\ClassLibrary1\ClassLibrary1\Class1.cs 11 32 ClassLibrary1

so now you are stuck with a permanent warning in your code. On top of this, it doesn't exactly solve your issue. What happens when something in the framework implicitly calls ToString() now? The result of the following code is that the code in the body of ToString() is still called:

        Foo myObj = new Foo();

        Console.WriteLine(myObj);

So now you have a warning in your code, and it doesn't actually prevent a developer from doing the same thing all over again. I think the right move here is to try to find a way to throw an appropriate exception at run time rather than trying to mess with the .net object contracts.

Suggestion for catching problem at compile time: I realized I had not previously given a suggestion for a solution for this issue. I don't really know what format your id is in for sure so I am only making a guess at it being an int, but why not protect whatever is creating the url with the querystring and pass the id as an int. That way a developer can't accidentally pass in some meaningless string without a compilation error. Like this for instance:

public string CreateItemUrl(int itemId)
{
   return string.Format("someurl.aspx?id={0}", itemId);
}

Now, calling this:

CreateItemUrl(myObj.Id);

becomes much more strongly typed and less error prone than:

string theUrl = string.Format("someurl.aspx?id={0}", myObj);
mockedobject
The warning can be supressed with a pragma. You make a good point about ToString being called implicitly still, I didn't consider that.I'll find it disappointing if I can't control what methods my class contains and I can't statically (at compile time) check my code.
A: 

Please, consider your design/opinion to be changed :)

First of all, definition of Foo.ToString does not define an override for Object.ToString() but a new one and should be prefixed with "new" keyword to prevent misunderstanding of semantics. Or explicitly declare an "override". IMHO, compiler issues corresponding warning.

Even if you'll find a way to prohibit a call to Foo.ToString, it will be prohibited at compile time only when type of "this" is known to be Foo or descendant, but ((object) foo).ToString() will be a correct workaround, because ToString is a method of Object interface.

Also, preventing a call of ToString is undesirable since Debugger uses it to present a value. SY, Jake

A: 

Aside from the bad design idea of overriding ToString and returning null (instead of string.Empty or something else), I think most of the answers miss the main point of the question, which is: how to throw a custom compiler error? I've actually been interested about knowing this myself.

Ricardo Villamil
The only way is by using the [Obsolete("reason")] attribute on the method or class.
Isak Savo
+1  A: 

I'd take a hybrid approach. (Hey, isn't SO about combing other answers? :))

First, create a new ToString that returns void. No return value means they can't use it to get any accidentally nice code:

public new void ToString() { }

Next, add the Obsolete attribute so when people DO call it, they get a warning telling them ToString is bad.

You won't need to override ToString this way, simply hide it with something that's useless. The fact it has no return will break all code, hence causing a compiler error on top of the obsolete message.

People casting to Object are not your concern, if I understand your question directly. You don't want to prevent people from calling ToString and getting type info, you want to prevent them from accidentally thinking ToString provides a useful result.

Edit: Please don't throw an exception or override ToString. That would cause "bad things" when your object is treated as an object. Just using "new" allows the benefits you asked for, without screwing up other frameworks.

MichaelGG